<?php

// Namespace

namespace Loop;


/**
 * @package		Loop
 * @category	Class
 */

class CDocumentation
{
	/**
	 * Generate the documentation for a file
	 *
	 * @param string 	$extensionId 	The extension Id
	 * @param string 	$className 		The class name without namespace
	 * @param string 	$directory 		The directory
	 *
	 * @return array 	An array with the details of the file
	 */

	public function generate($extensionId, $className, $directory)
	{
		// Get the object reflection

		app()->getObjectFactory()->getObjectReflection($extensionId, $className, $directory, $objectReflection, $pathToFile);


		// Initialize result

		$result = array();


		// Store the namespace,classNames,extensionId,directory,namespace

		$result['classNameWithoutNamespace'] = $className;
		$result['className'] = $objectReflection->getName();
		$result['namespace'] = $objectReflection->getNamespaceName();
		$result['extensionId'] = $extensionId;
		$result['directory'] = $directory;


		// Get the parent class

		$parentClass = $objectReflection->getParentClass();


		// Is there a parent?

		if (empty($parentClass) === false)
		{
			// Yes, store it

			$objectReflectionParent = new \ReflectionClass($parentClass->name);


			$result['parentNameSpace'] = $objectReflectionParent->getNamespaceName();						
			$result['parentClassName'] = $objectReflectionParent->getName();
			$result['parentClassNameWithoutNamespace'] = str_replace($result['parentNameSpace'] . '\\' , '',  $objectReflectionParent->getName());		
		}


		// Get the documentation

		$this->getDocumentation($objectReflection, $result, $pathToFile);


		return $result;
	}

	
	/**
	 * Get the dependencies of the file
	 *
	 * @param 	string 	$pathToFile 	The path to the file
	 * @param 	object 	$result 				The result
	 */

	private function getDependencies($pathToFile, &$result)
	{
		// Get the content of the file

		$content = file_get_contents($pathToFile);


		// Initialize  variables

		$dependencies = array();
		$result['dependencies'] = array();


		// Get the dependencies

		preg_match_all('/require_once\((.*)\);/', $content, $matchesDependencies);

		if (count($matchesDependencies) == 2)
		{
			if (count($matchesDependencies[1]) > 0)
			{
				foreach($matchesDependencies[1] as $dependency)
				{
					if (array_search($dependency, $dependencies) === false)
					{
						$dependencies[] = $dependency;
					}
				}
			}
		}



		// For each dependency found

		foreach ($dependencies as $dependency)
		{
			// Find the path

			preg_match_all('/(PATH_([A-Z]+)_([A-Z]+))|(PATH_([A-Z]+))/', $dependency, $matchesDependencies);

			if (count($matchesDependencies) == 6)
			{
				if (count($matchesDependencies[1]) > 0)
				{
					$extensionId = app()->getPathHelper()->convertPathToExtensionId(constant($matchesDependencies[0][0]));


					// Is this a class ?

					preg_match_all('/\'Class\/((.*)+)(\.php)/', $dependency, $matchesClasses);

					if (count($matchesClasses) == 4)
					{
						if (count($matchesClasses[1]) > 0)
						{						
							$result['dependencies'][$extensionId]['Class'][] = $matchesClasses[1][0];

							continue;
						}
					}


					// Is this a model ?		

					preg_match_all('/\'Model\/((.*)+)(\.php)/', $dependency, $matchesModels);

					if (count($matchesModels) == 4)
					{
						if (count($matchesModels[1]) > 0)
						{						
							$result['dependencies'][$extensionId]['Model'][] = $matchesModels[1][0];

							continue;
						}
					}


					// It's something else

					preg_match_all('/\'(.*)\'/', $dependency, $matchesOthers);

					if (count($matchesOthers) == 2)
					{
						if (count($matchesOthers[1]) > 0)
						{
							$result['dependencies'][$extensionId]['Other'][] = $matchesOthers[1][0];

							continue;							
						}
					}
				}
			}
		}
	}


	/**
	 * Get the datas for create the html page
	 *
	 * @param 	object 	$objectReflection 			The object to translate
	 * @param 	array 	$result 					The result 
	 * @param 	string 	$pathToFile					The path to the file
	 */

	private function getDocumentation(&$objectReflection, &$result, $pathToFile)
	{
		// Variables

		$rawPrivateFunctions = array();
		$rawProtectedFunctions = array();
		$rawPublicFunctions = array();
		$rawStaticFunctions = array();
		$result['hasProperties'] = 0;
		$result['hasMethods'] = 0;


		// Get description of the file

		$fileDocComment = $objectReflection->getDocComment();

			// Get the params

		$result['description']['params'] = $this->getFileParams($fileDocComment);

			// Get the description

		$result['description']['infos'] = $this->getFileDescription($fileDocComment);

		
		// Get methods statics

		$result['staticFunctions'] =  $this->processMethodsOrProperties(\ReflectionMethod::IS_STATIC, $objectReflection, $result, 'methods');


		// Get methods public

		$result['publicFunctions'] = $this->processMethodsOrProperties(\ReflectionMethod::IS_PUBLIC, $objectReflection, $result, 'methods');


		// Get methods protected

		$result['protectedFunctions'] = $this->processMethodsOrProperties(\ReflectionMethod::IS_PROTECTED, $objectReflection, $result, 'methods');


		// Get methods private

		$result['privateFunctions'] = $this->processMethodsOrProperties(\ReflectionMethod::IS_PRIVATE, $objectReflection, $result, 'methods');


		// Store if there is methods or not

		if 
		(
			count($result['staticFunctions']) > 0 ||
			count($result['publicFunctions']) > 0 ||
			count($result['protectedFunctions']) > 0 ||
			count($result['privateFunctions']) > 0 

		)
		{
			$result['hasMethods'] = 1;
		}



		// Get constant

		$result['constants'] = $objectReflection->getConstants();		


		// Get properties statics

		$result['staticProperties'] =  $this->processMethodsOrProperties(\ReflectionProperty::IS_STATIC, $objectReflection, $result, 'properties', $pathToFile);


		// Get properties public

		$result['publicProperties'] = $this->processMethodsOrProperties(\ReflectionProperty::IS_PUBLIC, $objectReflection, $result, 'properties', $pathToFile);


		// Get properties protected

		$result['protectedProperties'] = $this->processMethodsOrProperties(\ReflectionProperty::IS_PROTECTED, $objectReflection, $result, 'properties', $pathToFile);


		// Get properties private

		$result['privateProperties'] = $this->processMethodsOrProperties(\ReflectionProperty::IS_PRIVATE, $objectReflection, $result, 'properties', $pathToFile);


		// Store if there is properties or not

		if 
		(
			count($result['staticProperties']) > 0 ||
			count($result['publicProperties']) > 0 ||
			count($result['protectedProperties']) > 0 ||
			count($result['privateProperties']) > 0 

		)
		{
			$result['hasProperties'] = 1;
		}


		// Initialize variable

		$result['objectProperties'] = array();
		

		// Get I/O

		$this->getIO($objectReflection, $result);


		// Is it an abstract Class  and a model ?

		if
		(
			$objectReflection->isAbstract() === false &&
			$result['directory'] == 'Model'
		)
		{
			// Instanciate the object

			$object = new $result['className'];


			// Get the object properties

			$objectProperties = $object->getProperties();

			foreach ($objectProperties as $type => $propertyDetails)
			{
				foreach ($propertyDetails as $name => $details)
				{
					$result['objectProperties'][$name] = array_merge($details, array('loopTypeProperty' => $type));
				}
			}

			ksort($result['objectProperties']);
		}
	

		// Get dependencies

		$this->getDependencies($pathToFile, $result);		
	}


	/**
	 * Get the description defined in the doc comment of the file
	 *
	 * @param string $docComment The doc comment
	 *
	 * @return string The description
	 */


	public function getFileDescription($docComment)
	{
		//

		$description = '';


		//

		preg_match_all('/\s\*\s([^@]+)/', $docComment, $matchesDescription);

		if (count($matchesDescription) == 2)
		{		
			// Value of the description mask 1, index 0

			if (isset($matchesDescription[1][0]) === true)
			{
				// Remove * and multiline and useless space

				$description = preg_replace('/(\s\*\/)|(\*\/)|\s\*|\n|(\s\s)+|(\*)+/', '', $matchesDescription[1][0]);
			}
		}


		return $description;			
	}


	/**
	 * Get the params defined in the doc comment of the file
	 *
	 * @param string $docComment The doc comment
	 *
	 * @return array An array with each name of param and value
	 */

	public function getFileParams($docComment)
	{
		// 

		$params = array();


		// Get the params

		preg_match_all('/(\s\*\s)(\@)([^\s]+)(\s)+(.*)/', $docComment, $matchesParams);

		if (count($matchesParams) == 6)
		{
			// Name of the param mask 3

			foreach ($matchesParams[3] as $key => $paramName)
			{
				$params[$key]['name'] = $paramName;
			}


			// Value of the param mask 5

			foreach ($matchesParams[5] as $key => $paramValue)
			{
				$params[$key]['value'] = $paramValue;
			}			
		}


		return $params;
	}

	
	/**
	 * Get the I/O of a model
	 *
	 * @param object  $objectReflection		The object reflection, pass by reference
	 * @param array   $result 				The result, pass by reference
	 */

	public function getIO(&$objectReflection, &$result)
	{
		// Initialize

		$result['ioProperties'] = array();


		// Is it an abstract Class  and a model ?

		if
		(
			$objectReflection->isAbstract() === false &&
			$result['directory'] == 'Model'
		)
		{
			$result['ioProperties'] = array
			(
				'action' => array
				(
					'pattern' => PATTERN_ACTION,
					'sqlType' => SQL_TYPE_ENUM,
					'instances' => str(PATTERN_ACTION . '?doc')
				),
				'source' => array
				(
					'pattern' => PATTERN_STRING_LONG,
					'sqlType' => SQL_TYPE_STRING_LONG,
					'instances' => str(PATTERN_STRING_LONG . '?doc')
				),
				'id' => array
				(
					'pattern' => PATTERN_INTEGER_UNSIGNED,
					'sqlType' => SQL_TYPE_INTEGER_UNSIGNED,
					'instances' => str(PATTERN_INTEGER_UNSIGNED . '?doc')
				),
				'dateOfCreation' => array
				(
					'pattern' => PATTERN_DATE_TIME,
					'sqlType' => SQL_TYPE_DATE_TIME,
					'instances' => str(PATTERN_DATE_TIME . '?doc')
				)
			);

			// staticProperties + localizedProperties
			// remove core properties
			// time becomes date
		

			// Instanciate the object

			$object = new $result['className'];


			// Get the object properties

			$objectProperties = $object->getProperties();
			
			foreach ($objectProperties as $type => $propertyDetails)
			{
			 
				foreach ($propertyDetails as $name => $details)
				{
					if
					(
						($details['isCore'] === true) ||
						($details['isSmart'] === true) ||
						($details['isComposition'] === true) ||
						($details['isAggregation'] === true) ||
						($details['isFile'] === true)
					)
					{
						continue;
					}

					if ($details['type'] === TYPE_TIMESTAMP)
					{
						$name = str_replace('timeOf', 'dateOf', $name);
					}

					$result['ioProperties'][$name] = array
					(
						'pattern' => $details['pattern'],
						'sqlType' => $details['sqlType'],
						'isLocalized' => $details['isLocalized'],
						'instances' => str($details['pattern'] . '?doc')
					);
				}
			}
		}
	}

	
	/**
	 * Get the params call define ine the docComment of a method
	 *
	 * @string $docComment The doc comment
	 *
	 * @return 	string The type of call
	 */

	public function getMethodCall($docComment)
	{
		//

		$result = null;


		//

		preg_match_all('/\s\*\s\@call[\s]+(.*)/', $docComment, $matchesCall);

		if 
		(
			count($matchesCall) == 2 && 
			empty($matchesCall[0][0]) === false
		)
		{	
			$result = $matchesCall[1][0];
		}	


		return $result;		
	}

	
	/**
	 * Get the description defined in the method doc comment
	 *
	 * @string $docComment The doc comment
	 *
	 * @return string 	The description
	 */

	public function getMethodDescription($docComment)
	{
		//

		$description = '';


		//

		preg_match_all('/\s\*\s([^@]+)/', $docComment, $matchesDescription);

		if (count($matchesDescription) == 2)
		{		
			// Value of the description mask 1, index 0

			if (isset($matchesDescription[1][0]) === true)
			{
				// Remove * and multiline and useless space

				$description = preg_replace('/(\*\/)|\s\*|\n|(\s\s)+|(\*)+/', '', $matchesDescription[1][0]);
			}
		}	


		return $description;		
	}


	/**
	 * Get a method description
	 *
	 * @param 	object 	$function 				The function
	 * @param 	object 	$functionReflection 	The function reflection 
	 * @param 	array 	$rawFunctions 			The array of the data for the function
	 */

	private function getMethodDocumentation(&$function, &$functionReflection, &$rawFunctions, $result)
	{
		// Get the doc comment

		$docComment = $functionReflection->getDocComment();

		
		// Does the method referer to another ?
		
		$this->getMethodReference($docComment);
		

		// Get the params

		$rawFunctions[$function->name]['description']['params'] = $this->getMethodArguments($docComment);


		// Get the rich documentation

		$rawFunctions[$function->name]['description']['richDocumentation'] =app()->getRichDocumentationManager()->getRichDocumentation($result['directory'], $result['extensionId'], $result['classNameWithoutNamespace'], $function->name);


		// Get the description

		$rawFunctions[$function->name]['description']['infos'] = $this->getMethodDescription($docComment);


		// Get the param return 

		$rawFunctions[$function->name]['description']['return'] = $this->getMethodReturn($docComment);
	}	


	/**
	 * Get the params define ine the docComment of a method
	 *
	 * @string $docComment The doc comment
	 *
	 * @return 	array 	An array with the list of parameters found, name, type, description
	 */

	public function getMethodArguments($docComment)
	{
		// Initialize variable

		$result = array();


		// Find the params

		preg_match_all('/\s\*\s\@param[\s]+(.*)/', $docComment, $matchesParams);

		if (count($matchesParams) == 2)
		{
			// mask 1

			foreach ($matchesParams[1] as $params)
			{
				// Colonne 1 : type, colonne 2 paramName, colonne 3 : description

				preg_match_all('/(.*\s)(\$[^\s]+)\s+(.*)/', $params, $matchesParamDetails);

				if (count($matchesParamDetails) == 4)
				{
					// Is there a param name ?

					if
					(
						isset($matchesParamDetails[2][0]) &&
						count($matchesParamDetails[2][0]) > 0
					)
					{
						// Is there a type ?

						if (count($matchesParamDetails[1][0]) > 0)
						{	
							$result[$matchesParamDetails[2][0]]['type'] = $matchesParamDetails[1][0];
						}	

						// Is there a description ?

						if (count($matchesParamDetails[3][0]) > 0)
						{	
							$result[$matchesParamDetails[2][0]]['description'] = $matchesParamDetails[3][0];
						}											

						$result[$matchesParamDetails[2][0]]['isRequired'] = 0;
					}
				}			
			}
		}


		// Get the list of arguments wich are required

		$argumentsRequired = $this->getMethodArgumentsRequired($docComment);


		// Get the list of default values

		$argumentsDefaultValues = $this->getMethodArgumentsDefaultValues($docComment);


		// For each argument

		foreach ($result as $argument => $details)
		{
			// Is it in the list of required?

			if (in_array($argument, $argumentsRequired) === true)
			{
				// Yes

				$result[$argument]['isRequired'] = 1;
			}

			// Is it in the list of default values?

			if (array_key_exists($argument, $argumentsDefaultValues) === true)
			{
				// Yes

				$result[$argument]['defaultValue'] = $argumentsDefaultValues[$argument];
			}			
		}


		return $result;
	}


	/**
	 * Get the params rquired define ine the docComment of a method
	 *
	 * @string $docComment The doc comment
	 *
	 * @return 	array 	An array with the list of parameters found, name, type, description
	 */

	public function getMethodArgumentsRequired($docComment)
	{
		// Initialize variable

		$result = array();


		// Find the params

		preg_match_all('/\s\*\s\@required[\s]+(.*)/', $docComment, $matchesParams);

		if (count($matchesParams) == 2)
		{
			// mask 1

			foreach ($matchesParams[1] as $param)
			{
				$result[] = trim($param);			
			}
		}


		return $result;
	}


	/**
	 * Get the params define ine the docComment of a method
	 *
	 * @string $docComment The doc comment
	 *
	 * @return 	array 	An array with the list of parameters found, name, type, description
	 */

	public function getMethodArgumentsDefaultValues($docComment)
	{
		// Initialize variable

		$result = array();


		// Find the params

		preg_match_all('/\s\*\s\@default[\s]+(.*)/', $docComment, $matchesParams);

		if (count($matchesParams) == 2)
		{
			// mask 1

			foreach ($matchesParams[1] as $params)
			{
				// Colonne 1 : variable, colonne 2 values

				preg_match_all('/(\$[^\s]+)=(.*)/', $params, $matchesParamDetails);

				if (count($matchesParamDetails) == 3)
				{
					// Is there values

					if
					(
						isset($matchesParamDetails[1][0]) === true &&
						isset($matchesParamDetails[2][0]) === true
					)
					{
						$result[trim($matchesParamDetails[1][0])] = trim($matchesParamDetails[2][0]);
					}
				}			
			}
		}


		return $result;
	}


	/**
	 * Get if the method refer to another
	 * 
	 * @param string $docComment The doc comment, pass by reference
	 */
	
	public function getMethodReference(&$docComment)
	{

		// Find the params \s\*\s\@see\s+(([a-z-A-Z]+\\)+)+(C.*)::(api_[a-z-A-Z]+)

		preg_match_all('/\s\*\s\@see\s+([a-z-A-Z\\\]+)+(C.*)::(api_[a-z-A-Z]+)/', $docComment, $matchesParams);

		
		if 
		(
			count($matchesParams) == 4 && 
			empty($matchesParams[0][0]) === false
		)
		{

			// Get the namespace
			
			$namespace = $matchesParams[1][0];
			
			
			// Remove the last /
			
			$namespace = rtrim($namespace, '\\');
			
			
			// Construct the name of the extension
			
			$extensionId = str_replace('\\', '-', $namespace);
			
			
			// Get the path to the extension
			
			$pathToExtension = app()->getPathHelper()->convertExtensionIdToPath($extensionId);
			
		
			
			// Get the class name
			
			$className = $matchesParams[2][0];
			
			
			// Get the function name
			
			$functionName = $matchesParams[3][0];
			
			
			//
			
			$directory = 'Class';
			
			
			// Is it a class or a model ?
			
			if (file_exists($pathToExtension . 'Class/' . $className . '.php') == false)
			{
				$directory = 'Model';
			} 
	
			
			// Get the object reflection

			app()->getObjectFactory()->getObjectReflection($extensionId, $className, $directory , $objectReflection, $pathToFile);
	
			
		
			$functionReflection = new \ReflectionMethod($objectReflection->name, $functionName);
			
			$docComment = $functionReflection->getDocComment();			
		}		
	}
	
	
	/**
	 * Get the params return define ine the docComment of a method
	 *
	 * @string $docComment The doc comment
	 *
	 * @return 	array 	An array with the return parameter found, type and description
	 */

	public function getMethodReturn($docComment)
	{
		//

		$result = array();


		//

		preg_match_all('/\s\*\s\@return[\s]+(.*)/', $docComment, $matchesReturn);

		if (count($matchesReturn) == 2)
		{	
			// mask 1

			foreach ($matchesReturn[1] as $return)
			{		
				// Is there a return ?

				if (isset($matchesReturn[1][0]) === true)
				{
					// Colonne 1 : type, colonne 2 : description

					preg_match_all('/([^\s]+)\s(.*)/', $matchesReturn[1][0], $matchesReturnDetails);
					
					// Is there a type ?

					if (isset($matchesReturnDetails[1][0]) === true)
					{	
						$result['type'] = $matchesReturnDetails[1][0];
					}	

					// Is there a description ?

					if (isset($matchesReturnDetails[2][0]) === true)
					{	
						$result['description'] = $matchesReturnDetails[2][0];
					}	
				}
			}
		}	


		return $result;		
	}
	
	
	/**
	 * Get a property description
	 *
	 * @param 	string 	$name 					The name of the property
	 * @param 	string 	$pathToFile 			The path to the file
	 * @param 	array 	$rawFunctions 			The array of the data for the function, pass by reference
	 * @pram 	int 	$type 					The type of the reflection property
	 */

	private function getPropertyDocumentation($name, $pathToFile, &$rawFunctions, $type)
	{
		// Get the content of the file

		$content = file_get_contents($pathToFile);


		// Define the type of property

		if ($type == \ReflectionProperty::IS_STATIC)
		{
			$typeProperty = 'static';
		}

		if ($type == \ReflectionProperty::IS_PUBLIC)
		{
			$typeProperty = 'public';
		}


		if ($type == \ReflectionProperty::IS_PROTECTED)
		{
			$typeProperty = 'protected';
		}


		if ($type == \ReflectionProperty::IS_PRIVATE)
		{
			$typeProperty = 'private';
		}


		// Construct the regex

		$regex = '/' . $typeProperty . '[\s]+\$' . $name . '.*\/\/[\s]+(.*)/';


		// Search the description 

		preg_match_all($regex, $content, $matchesComment);

		if (count($matchesComment) == 2)
		{
			if (count($matchesComment[1]) > 0)
			{
				$rawFunctions[$name]['description'] = $matchesComment[1][0];
			}
		}
	}	


	/**
	 * Get the details for a method or a propertie
	 * 
	 * @param  	string 	$type 				The type : static/public/private/protected
	 * @param 	object 	$objectReflection 	The class reflection, pass by reference
	 * @param 	array 	$result 			The result, path by reference
	 * @param 	string 	$action 			The action : properties/method
	 * @pram 	string 	$pathToFile 		The path to the file, optional
	 */
	
	private function processMethodsOrProperties($type, &$objectReflection, &$result, $action, $pathToFile = null)
	{
		// Initialize variable

		$rawFunctions = array();


		// Construct the call

		$call = 'get' . ucfirst($action);


		// Get the methods

		$functions = $objectReflection->$call($type);


		// For each static function found

		foreach ($functions as $function) 
		{
			// If it belongs to his parent, continue

			if 
			(
				$function->class != $result['className']
			)
			{
				continue;
			}


			// Is it a method ?

			if ($action == 'methods')
			{
				// Store the method  and his parameters

				$rawFunctions[$function->name] = array();


				$functionReflection = new \ReflectionMethod($objectReflection->name, $function->name);


				// Get the comments

				$this->getMethodDocumentation($function, $functionReflection, $rawFunctions, $result);

				$rawFunctions[$function->name]['params'] = $functionReflection->getParameters();


				// Is it an api ?

				preg_match_all('/^api_.*/', $function->name, $matchesApi);

				if (count($matchesApi) == 1)
				{
					if (count($matchesApi[0]) > 0)
					{
						// Store api and his parameters

						$result['API'][$function->name] = $rawFunctions[$function->name];

						unset($rawFunctions[$function->name]);
					}
				}

			}


			// Is it a property?
			
			if ($action == 'properties')
			{
				// Store it

				$rawFunctions[$function->name] = array();


				// Get the documentation

				$this->getPropertyDocumentation($function->name, $pathToFile, $rawFunctions, $type);	
			}			
		}


		return $rawFunctions;
	}
}

?>
